/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.auth.internal.kerberos.v5;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Hashtable;

/**
 * Kerberos configuration.
 * 
 * Parses Kerberos configuration file according to the following syntax:<br>
 * CONFIG = (SECTION)*<br>
 * SECTION = '[' name ']' (EXPR)*<br>
 * EXPR = S | C<br>
 * S = tag '=' value<br>
 * C = tag '=' '{' (EXPR)* '}'<br>
 * 
 * @see http 
 *      ://web.mit.edu/kerberos/www/krb5-1.5/krb5-1.5.1/doc/krb5-admin/krb5.conf
 *      .html#krb5.conf
 */
public class KrbConfig {

	private static final int TT_START_SECTION = '[';

	private static final int TT_END_SECTION = ']';

	private static final int TT_EQAUL = '=';

	/**
	 * Search Kerberos configuration
	 * 
	 * @return - configuration file or null if there is no one
	 * @throws IOException
	 *             - in case of I/O errors
	 */
	public static KrbConfig getSystemConfig() throws IOException {

		String fName = System.getProperty("java.security.krb5.conf"); //$NON-NLS-1$
		if (fName == null) {

			fName = System.getProperty("java.home") + "/lib/security/krb5.conf"; //$NON-NLS-1$ //$NON-NLS-2$
			final File f = new File(fName);
			if (f.exists()) {
				return new KrbConfig(f);
			}

			final String OSName = System.getProperty("os.name"); //$NON-NLS-1$
			if (OSName.indexOf("Windows") != -1) { //$NON-NLS-1$
				fName = "c:\\winnt\\krb5.ini"; //$NON-NLS-1$
			} else if (OSName.indexOf("Linux") != -1) { //$NON-NLS-1$
				fName = "/etc/krb5.conf"; //$NON-NLS-1$
			} else {
				throw new UnsupportedOperationException(OSName);
			}
		}

		final File f = new File(fName);
		if (f.exists() && f.isFile()) {
			return new KrbConfig(f);
		}
		return null;
	}

	private final Hashtable<String, Hashtable<String, String>> values = new Hashtable<String, Hashtable<String, String>>();

	/**
	 * Creates configuration.
	 * 
	 * @param f
	 *            - configuration file
	 * @throws IOException
	 *             - in case of I/O errors
	 */
	public KrbConfig(File f) throws IOException {
		final StreamTokenizer t = new StreamTokenizer(new InputStreamReader(
				new FileInputStream(f)));

		t.commentChar('#');

		t.ordinaryChar(TT_START_SECTION);
		t.ordinaryChar(TT_END_SECTION);
		t.ordinaryChar(TT_EQAUL);

		t.wordChars('_', '_');
		t.wordChars(':', ':');
		t.wordChars('/', '/');

		t.nextToken(); // init stream tokenizer

		String currentSection = nextSection(t);
		while (currentSection != null) {

			// allow duplicate section
			Hashtable<String, String> sectionValues = values
					.get(currentSection);
			if (sectionValues == null) {
				sectionValues = new Hashtable<String, String>();
				values.put(currentSection, sectionValues);
			}

			// parse section content
			parseSection(t, sectionValues);

			currentSection = nextSection(t);
		}
	}

	public String getValue(String section, String tag) {
		final Hashtable<String, String> h = values.get(section);
		if (h != null) {
			return h.get(tag);
		}
		return null;
	}

	private String nextSection(StreamTokenizer t) throws IOException {
		while (true) {
			if (t.ttype == StreamTokenizer.TT_EOF) {
				return null;
			}
			if (t.ttype != TT_START_SECTION) {
				t.nextToken();
				continue;
			}

			t.nextToken();
			if (t.ttype != StreamTokenizer.TT_WORD) {
				t.nextToken();
				continue;
			}
			final String section = t.sval;

			t.nextToken();
			if (t.ttype != TT_END_SECTION) {
				t.nextToken();
				continue;
			}
			t.nextToken();
			return section;
		}
	}

	// TODO: implement parsing values in curly braces and decide
	// how to represent them
	private void parseSection(StreamTokenizer t,
			Hashtable<String, String> sectionValues) throws IOException {

		while (true) {
			// end of file or new section
			if (t.ttype == StreamTokenizer.TT_EOF
					|| t.ttype == TT_START_SECTION) {
				return;
			}

			// tag
			if (t.ttype != StreamTokenizer.TT_WORD) {
				t.nextToken();
				continue;
			}
			final String tag = t.sval;
			t.nextToken();

			// equals char
			if (t.ttype != TT_EQAUL) {
				t.nextToken();
				continue;
			}
			t.nextToken();

			// value
			if (t.ttype != StreamTokenizer.TT_WORD) {
				t.nextToken();
				continue;
			}
			final String value = t.sval;
			t.nextToken();

			sectionValues.put(tag, value);
		}
	}
}
